usingnamespace @import("mp.zig");

pub fn U8Iterator(comptime ReaderType: type) type {
    return struct {
        const Self = @This();
        
        reader: ReaderType,
        i: usize,
        n: usize,

        pub fn next(self: *Self) !u8 {
            if (!self.hasNext()) return error.HasNoNext;
            self.i += 1;
            return self.reader.readByte();
        }

        pub fn hasNext(self: Self) bool {
            return self.i < self.n;
        }
    };
}

pub fn ArrayIterator(comptime ReaderType: type) type {
    return struct {
        const Self = @This();

        reader: ReaderType,
        i: usize,
        n: usize,

        pub fn next(self: *Self, comptime T: type) !T {
            if (!self.hasNext()) return error.HasNoNext;
            self.i += 1;
            return decodeAnyIntoTypeStatic(T, self.reader);
        }

        pub fn hasNext(self: Self) bool {
            return self.i < self.n;
        }
    };
}

pub fn MapIterator(comptime ReaderType: type) type {
    return struct {
        const Self = @This();
        
        reader: ReaderType,
        i: usize,
        n: usize,

        pub fn next(self: *Self, comptime K: type, comptime V: type) !struct{ key: K, value: V } {
            if (!self.hasNext()) return error.HasNoNext;
            self.i += 1;
            return .{
                .key = try decodeAnyIntoTypeStatic(K, self.reader),
                .value = try decodeAnyIntoTypeStatic(V, self.reader),
            };
        }

        pub fn hasNext(self: Self) bool {
            return self.i < self.n;
        }
    };
}

pub fn decodeStringIterator(reader: anytype) void {
    const len = try decodeStringLength(reader);

    return U8Iterator(@typeOf(reader)) {
        .reader = reader,
        .i = 0,
        .n = len,
    };
}

pub fn decodeBinaryIterator(reader: anytype) !U8Iterator(@typeOf(reader)) {
    const len = try decodeBinaryLength(reader);

    return U8Iterator(@typeOf(reader)) {
        .reader = reader,
        .i = 0,
        .n = len,
    };
}

pub fn decodeArrayIterator(reader: anytype) !ArrayIterator(@TypeOf(reader)) {
    const len = try decodeArrayLength(reader);

    return ArrayIterator(@TypeOf(reader)) {
        .reader = reader,
        .i = 0,
        .n = len,
    };
}

pub fn decodeMapIterator(reader: anytype) !MapIterator(@TypeOf(reader)) {
    const len = try decodeMapLength(reader);

    return MapIterator(@TypeOf(reader)) {
        .reader = reader,
        .i = 0,
        .n = len,
    };
}

pub fn decodeAnyIntoTypeStatic(comptime T: type, reader: anytype) !T {
    const info = @typeInfo(T);

    switch (info) {
        .Bool => try decodeBool(reader),

        .Int => |i| {
            const value = switch (i.signedness) {
                .signed => try decodeSInt(reader),
                .unsigned => try decodeUInt(reader),
            };

            if (value > std.math.maxInt(T)) {
                return error.UnexpectedMessage;
            }

            return @intCast(T, value);
        },

        .Float => |i| {
            const value = try decodeFloat(reader);
            const max_value = switch(i.bits) {
                32 => std.math.max_f32,
                64 => std.math.max_f64,
                else => @compileError("only floats f32 and f64 are supported"),
            };

            if (value > max_value) {
                return error.UnexpectedMessage;
            }

            return @floatCast(T, value);
        },

        .Optional => |i| {
            // look-ahead parsing needed here
            var peek_reader = std.io.peekStream(1, reader);
            const m = try peek_reader.reader().readIntBig(u8);
            try peek_reader.putBackByte(m);

            const result = decodeNil(peek_reader.reader());

            return if (result) |_| null
            else |err| switch (err) {
                error.UnexpectedMessage => {
                    try peek_reader.putBackByte(m);
                    return try decodeAnyIntoTypeStatic(i.child, peek_reader.reader());
                },
                else => err,
            };
        
        },
        .Array => |i| {},
        .Struct => |i| {},
        else => @compileError("type " ++ @typeName(T) ++ " not supported"),
    }
}

const testing = std.testing;
const fixedBufferStream = std.io.fixedBufferStream;

test "decodeArrayIterator" {
    const bytes = [_]u8{ 0x92, 0x00, 0x01 };
    var fbs = fixedBufferStream(&bytes);

    var iter = try decodeArrayIterator(fbs.reader());

    try testing.expect(iter.hasNext());
    try testing.expectEqual(try iter.next(u32), 0);
    try testing.expect(iter.hasNext());
    try testing.expectEqual(try iter.next(u32), 1);
    try testing.expect(!iter.hasNext());
}
